diff options
Diffstat (limited to 'app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx')
| -rw-r--r-- | app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx | 160 |
1 files changed, 122 insertions, 38 deletions
diff --git a/app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx b/app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx index 0f783375..20f0ea51 100644 --- a/app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx +++ b/app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx @@ -6,14 +6,20 @@ import { Skeleton } from "@/components/ui/skeleton" import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" import { Shell } from "@/components/shell" import { InformationButton } from "@/components/information/information-button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { FileText, Calendar, AlertTriangle, ArrowLeft } from "lucide-react" +import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage } from "@/components/ui/breadcrumb" +import { formatDateTime } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import Link from "next/link" -import { - getGtcClauses, +import { + getGtcClauses, getUsersForFilter, } from "@/lib/gtc-contract/gtc-clauses/service" import { getGtcDocumentById } from "@/lib/gtc-contract/service" import { searchParamsCache } from "@/lib/gtc-contract/gtc-clauses/validations" -import { GtcClausesPageHeader } from "@/lib/gtc-contract/gtc-clauses/gtc-clauses-page-header" import { GtcClausesTable } from "@/lib/gtc-contract/gtc-clauses/table/clause-table" interface GtcClausesPageProps { @@ -51,38 +57,38 @@ export default async function GtcClausesPage(props: GtcClausesPageProps) { return ( <Shell className="gap-2"> - {/* 헤더 컴포넌트 */} - <GtcClausesPageHeader document={document} /> - - {/* 문서 정보 카드 */} - <div className="rounded-lg border bg-card p-4"> - <div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm"> - <div> - <div className="font-medium text-muted-foreground">최초등록일</div> - <div>{document.createdAt ? new Date(document.createdAt).toLocaleDateString('ko-KR') : '-'}</div> - </div> - <div> - <div className="font-medium text-muted-foreground">최초등록자</div> - <div>{document.createdByName || '-'}</div> - </div> - <div> - <div className="font-medium text-muted-foreground">최종수정일</div> - <div>{document.updatedAt ? new Date(document.updatedAt).toLocaleDateString('ko-KR') : '-'}</div> - </div> - <div> - <div className="font-medium text-muted-foreground">최종수정자</div> - <div>{document.updatedByName || '-'}</div> - </div> - </div> - - {document.editReason && ( - <div className="mt-3 pt-3 border-t"> - <div className="font-medium text-muted-foreground mb-1">최종 편집사유</div> - <div className="text-sm">{document.editReason}</div> - </div> - )} + + <div className="flex items-center justify-between"> + <Breadcrumb> + <BreadcrumbList> + <BreadcrumbItem> + <BreadcrumbLink href="/evcp">EVCP</BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + <BreadcrumbItem> + <BreadcrumbLink href="/evcp/gtc">GTC 목록 관리</BreadcrumbLink> + </BreadcrumbItem> + <BreadcrumbSeparator /> + <BreadcrumbItem> + <BreadcrumbPage>GTC 조항 관리</BreadcrumbPage> + </BreadcrumbItem> + </BreadcrumbList> + </Breadcrumb> + + <Button asChild variant="outline" size="sm"> + <Link href="/evcp/basic-contract"> + <ArrowLeft className="h-4 w-4 mr-2" /> + 목록으로 돌아가기 + </Link> + </Button> </div> + {/* 템플릿 정보 섹션 (콤팩트) */} + <React.Suspense fallback={<TemplateInfoSkeleton />}> + <DocumentInfo document={document} /> + </React.Suspense> + + {/* 조항 테이블 */} <React.Suspense fallback={ @@ -96,7 +102,7 @@ export default async function GtcClausesPage(props: GtcClausesPageProps) { } > <GtcClausesTable - promises={promises} + promises={promises} documentId={documentId} document={document} /> @@ -109,7 +115,7 @@ export default async function GtcClausesPage(props: GtcClausesPageProps) { export async function generateMetadata(props: GtcClausesPageProps) { const params = await props.params const documentId = parseInt(params.id) - + if (isNaN(documentId)) { return { title: "GTC 조항 관리", @@ -118,7 +124,7 @@ export async function generateMetadata(props: GtcClausesPageProps) { try { const document = await getGtcDocumentById(documentId) - + if (!document) { return { title: "GTC 조항 관리", @@ -126,7 +132,7 @@ export async function generateMetadata(props: GtcClausesPageProps) { } const title = `GTC 조항 관리 - ${document.type === "standard" ? "표준" : "프로젝트"} v${document.revision}` - const description = document.project + const description = document.project ? `${document.project.name} (${document.project.code}) 프로젝트의 GTC 조항을 관리합니다.` : "표준 GTC 조항을 관리합니다." @@ -139,4 +145,82 @@ export async function generateMetadata(props: GtcClausesPageProps) { title: "GTC 조항 관리", } } -}
\ No newline at end of file +} + + +async function DocumentInfo({ + document, +}: { + document: Promise<Awaited<ReturnType<typeof getGtcDocumentById>>> +}) { + const documentInfo = await document + + if (!documentInfo) { + return ( + <Card> + <CardContent className="py-6"> + <div className="text-center text-gray-500"> + <AlertTriangle className="h-8 w-8 mx-auto mb-2" /> + <p>템플릿 정보를 찾을 수 없습니다.</p> + </div> + </CardContent> + </Card> + ) + } + + return ( + <Card> + <CardHeader className="py-4"> + <div className="flex flex-wrap items-center justify-between gap-2"> + {/* 좌측: 제목 + 리비전 */} + <div className="flex items-center gap-2 min-w-0"> + <CardTitle className="text-lg font-semibold leading-none truncate"> + {documentInfo.title} + </CardTitle> + <Badge variant="outline" className="shrink-0">v{documentInfo.revision}</Badge> + </div> + + <div className="flex items-center gap-2"> + <Badge + variant={documentInfo.isActive ? "default" : "secondary"} + className="shrink-0" + > + {documentInfo.isActive ? "활성" : "비활성"} + </Badge> + </div> + </div> + + {/* 메타 정보 한 줄 정리 */} + <div className="mt-1 flex flex-wrap items-center gap-2 text-xs text-muted-foreground"> + <span className="inline-flex items-center gap-1"> + <Calendar className="h-3.5 w-3.5" /> + 생성일: {formatDateTime(documentInfo.createdAt, "KR")} + </span> + {documentInfo.fileName && ( + <> + <span className="select-none">•</span> + <span className="inline-flex items-center gap-1 min-w-0"> + <FileText className="h-3.5 w-3.5" /> + <span className="truncate max-w-[52ch]">템플릿 파일: {documentInfo.fileName}</span> + </span> + </> + )} + </div> + </CardHeader> + </Card> + ) +} + +// 로딩 스켈레톤 (콤팩트) +function TemplateInfoSkeleton() { + return ( + <Card> + <CardHeader className="py-4"> + <div className="space-y-2"> + <div className="h-5 bg-gray-200 rounded w-1/2 animate-pulse" /> + <div className="h-3 bg-gray-200 rounded w-1/3 animate-pulse" /> + </div> + </CardHeader> + </Card> + ) +} |
